package ar.uade.edu.fain.ad.server.dao.jpa;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.PersistenceException;

import org.hibernate.Session;

import ar.uade.edu.fain.ad.server.dao.DataAccessException;
import ar.uade.edu.fain.ad.server.dao.GenericDAO;
import ar.uade.edu.fain.ad.server.util.persistence.HibernateTransactionManager;
import ar.uade.edu.fain.ad.server.util.persistence.HibernateUtil;
import ar.uade.edu.fain.ad.server.util.persistence.TransactionManager;

public abstract class GenericJpaDAO<B, E, K extends Serializable> 
		implements GenericDAO<B, K> {

	private Class<?> type;

	public GenericJpaDAO(Class<?> type) {
		super();
		this.type = type;
	}

	public Class<?> getType() {
		return type;
	}

	protected abstract E toEntity(B business);

	protected abstract B toBusiness(E entity);

	public static interface Query<T> {

		public abstract T inTransaction(Session session)
				throws PersistenceException;

	}

	public static interface Action {

		public abstract void inTransaction(Session session)
				throws PersistenceException;

	}

	protected final List<E> toEntity(final Iterable<B> businessIt) {
		final ArrayList<E> entities = new ArrayList<E>();
		for (B business : businessIt) {
			entities.add(toEntity(business));
		}
		return entities;
	}
	

	protected final List<B> toBusiness(final Iterable<E> entitiesIt) {
		final ArrayList<B> business = new ArrayList<B>();
		for (E entity : entitiesIt) {
			business.add(toBusiness(entity));
		}
		return business;
	}

	protected void execute(final Action action) throws DataAccessException {
		execute(new Query<Void>() {
			@Override
			public Void inTransaction(Session session)
					throws PersistenceException {
				action.inTransaction(session);
				return null;
			}
		});
	}

	protected <T> T execute(final Query<T> query) throws DataAccessException {
		final Session session = getSession();
		try {
			getTransactionManager().start();
			T result = query.inTransaction(session);
			return result;
		} catch (RuntimeException re) {
			try {
				getTransactionManager().rollback();
			} catch (Exception e) {
			}
			throw new DataAccessException(
					"Error ejecutando action en contexto transactional.", re);
		}

	}

	protected TransactionManager getTransactionManager() {
		return HibernateTransactionManager.get();
	}

	protected Session getSession() {
		return HibernateUtil.get().getSession();
	}

	@Override
	public B get(final K key) {
		return execute(new Query<B>() {
			@SuppressWarnings("unchecked")
			@Override
			public B inTransaction(final Session session) 
					throws PersistenceException {
				E entity = (E) session.get(getType(), key);
				return entity == null ? null : toBusiness(entity);
			}
		});
	}
	
	@Override
	public List<B> getAll() {
		return execute(new Query<List<B>>() {
			@Override
			public List<B> inTransaction(Session session)
					throws PersistenceException {
				@SuppressWarnings("unchecked")
				final List<E> results = session.createQuery(
						"SELECT e FROM " + getType().getSimpleName() + " e")
						.list();
				final ArrayList<B> business = new ArrayList<B>();
				for (E entity : results) {
					business.add(toBusiness(entity));
				}
				return business;
			}
		});
	}
	
	@Override
	public void save(final B business) {
		execute(new Action() {
			@Override
			public void inTransaction(Session session) 
					throws PersistenceException {
				E entity = toEntity(business);
				session.persist(entity);
				onEntityPersisted(entity, business);
			}
		});
	}
	/**
	 * Si hay que planchar el id generado, el muchacho se anima a hacerlo solo. 
	 * Esta es la MAGIA que me gusta.
	 * @param entity
	 * @param business
	 */
	protected void onEntityPersisted(E entity, B business) {
		for (Method methodBusiness : business.getClass().getMethods()) {
			if (methodBusiness.getName().equals("setId")) {
				for (Method methodEntity : entity.getClass().getMethods()) {
					if (methodEntity.getName().equals("getId")) {
						try {
							methodBusiness.invoke(business, 
									methodEntity.invoke(entity));
						} catch (Exception e) {
							System.err.println("(GenericDAO) "
									+ "Magia pegamento de ID: [FAIL] - " 
									+ e.getMessage());
						}
					}
				}
			}
		}
	}

	@Override
	public void delete(final K key) {
		execute(new Action() {
			@Override
			public void inTransaction(Session session) 
					throws PersistenceException {
				B entity = get(key);
				if (entity != null) {
					session.delete(entity);
				}
			}
		});
	}
	
	@Override
	public void update(final B business) {
		execute(new Action() {
			@Override
			public void inTransaction(Session session) 
					throws PersistenceException {
				session.merge(toEntity(business));	
			}
		});
	}
	
}
